module utils_mod

  use netcdf
  use flogger

  implicit none

contains

  subroutine read_grid(file_path, nlon, nlat, nlev, lon, lat, lev)

    character(*), intent(in) :: file_path
    integer, intent(inout) :: nlon
    integer, intent(inout) :: nlat
    integer, intent(inout) :: nlev
    real(8), intent(inout), allocatable :: lon(:)
    real(8), intent(inout), allocatable :: lat(:)
    real(8), intent(inout), allocatable :: lev(:)

    integer ierr, ncid
    integer lon_dimid, lat_dimid, lev_dimid
    integer lon_varid, lat_varid, lev_varid

    call handle_netcdf_error(nf90_open(file_path, nf90_netcdf4, ncid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_dimid(ncid, 'lon', lon_dimid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_dimid(ncid, 'lat', lat_dimid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_dimid(ncid, 'lev', lev_dimid), __file__, __line__)
    call handle_netcdf_error(nf90_inquire_dimension(ncid, lon_dimid, len=nlon), __file__, __line__)
    call handle_netcdf_error(nf90_inquire_dimension(ncid, lat_dimid, len=nlat), __file__, __line__)
    call handle_netcdf_error(nf90_inquire_dimension(ncid, lev_dimid, len=nlev), __file__, __line__)

    allocate(lon(nlon), lat(nlat), lev(nlev))

    call handle_netcdf_error(nf90_inq_varid(ncid, 'lon', lon_varid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_varid(ncid, 'lat', lat_varid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_varid(ncid, 'lev', lev_varid), __file__, __line__)
    call handle_netcdf_error(nf90_get_var(ncid, lon_varid, lon), __file__, __line__)
    call handle_netcdf_error(nf90_get_var(ncid, lat_varid, lat), __file__, __line__)
    call handle_netcdf_error(nf90_get_var(ncid, lev_varid, lev), __file__, __line__)
    call handle_netcdf_error(nf90_close(ncid), __file__, __line__)

  end subroutine read_grid

  subroutine read_data_3d(file_path, var_name, var)

    character(*), intent(in) :: file_path
    character(*), intent(in) :: var_name
    real(8), intent(inout) :: var(:,:,:)

    integer ncid, varid
    real add_offset, scale_factor

    call log_notice('Reading ' // trim(file_path) // '...')
    call handle_netcdf_error(nf90_open(file_path, nf90_netcdf4, ncid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_varid(ncid, var_name, varid), __file__, __line__)
    call handle_netcdf_error(nf90_get_var(ncid, varid, var), __file__, __line__)
    if (nf90_get_att(ncid, varid, 'add_offset', add_offset) == nf90_noerr .and. &
        nf90_get_att(ncid, varid, 'scale_factor', scale_factor) == nf90_noerr) then
      var = scale_factor * var + add_offset
    end if
    call handle_netcdf_error(nf90_close(ncid), __file__, __line__)

  end subroutine read_data_3d

  subroutine read_data_4d(file_path, var_name, var)

    character(*), intent(in) :: file_path
    character(*), intent(in) :: var_name
    real(8), intent(inout) :: var(:,:,:,:)

    integer ncid, varid
    real add_offset, scale_factor

    call log_notice('Reading ' // trim(file_path) // '...')
    call handle_netcdf_error(nf90_open(file_path, nf90_netcdf4, ncid), __file__, __line__)
    call handle_netcdf_error(nf90_inq_varid(ncid, var_name, varid), __file__, __line__)
    call handle_netcdf_error(nf90_get_var(ncid, varid, var), __file__, __line__)
    if (nf90_get_att(ncid, varid, 'add_offset', add_offset) == nf90_noerr .and. &
        nf90_get_att(ncid, varid, 'scale_factor', scale_factor) == nf90_noerr) then
      var = scale_factor * var + add_offset
    end if
    call handle_netcdf_error(nf90_close(ncid), __file__, __line__)

  end subroutine read_data_4d

  subroutine handle_netcdf_error(ierr, file, line)

    integer, intent(in) :: ierr
    character(*), intent(in) :: file
    integer, intent(in) :: line

    if (ierr /= nf90_noerr) then
      call log_error('NetCDF error: ' // trim(nf90_strerror(ierr)) // '!', file, line)
    end if

  end subroutine handle_netcdf_error

end module utils_mod
